﻿/* **********************************************************************************
 * Copyright (c) 2011 John Hughes
 *
 * Elve Touch is licenced under the Microsoft Reciprocal License (Ms-RL).
 *
 * Project Website: http://elvetouch.codeplex.com/
 * **********************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing.Imaging;
using System.Drawing;

namespace ElveTouch
{
    public class BinaryStreamWriter : IDisposable
    {
        MemoryStream _ms;
        BinaryWriter _bw; // BinaryWriter stores this data type in little endian format.

        public BinaryStreamWriter()
        {
            _ms = new MemoryStream();
            _bw = new BinaryWriter(_ms, Encoding.UTF8);
        }

        public void Write(Rectangle rect)
        {
            _bw.Write((Int16)rect.X);
            _bw.Write((Int16)rect.Y);
            _bw.Write((Int16)rect.Width);
            _bw.Write((Int16)rect.Height);
        }

        public void Write(Point p)
        {
            _bw.Write((Int16)p.X);
            _bw.Write((Int16)p.Y);
        }

        public void Write(Size s)
        {
            _bw.Write((Int16)s.Width);
            _bw.Write((Int16)s.Height);
        }

        public void Write(Color c)
        {
            _bw.Write((byte)c.A);
            _bw.Write((byte)c.R);
            _bw.Write((byte)c.G);
            _bw.Write((byte)c.B);
            //_bw.Write(c.ToArgb()); // DON'T USE ToArgb() since it doesn't really write the bytes n ARGB order.... It's little endian.
        }

        public void Write(Font f)
        {
            this.Write(f.Name); // string
            this.Write(f.Size); // float
            _bw.Write((byte)f.Style);
        }

        public void Write(string s)
        {
            _bw.Write((Int32)s.Length);
            _bw.Write(Encoding.UTF8.GetBytes(s)); // no BOM
        }

        public void Write(Byte b)
        {
            _bw.Write(b);
        }

        public void Write(UInt16 i)
        {
            _bw.Write(i);
        }

        public void Write(Int32 i)
        {
            _bw.Write(i);
        }

        public void Write(UInt32 i)
        {
            _bw.Write(i);
        }

        public void Write(float f)
        {
            // Based on Reflector this writes out the actual 4 bytes that are stored in memory for the float.
            // Single precision IEEE-754 floating point number
            // in little endian format

            // http://kirkwylie.blogspot.com/2008/11/ieee-754-floating-point-binary.html

            _bw.Write(f);
        }

        public void Write(double d)
        {
            // Based on Reflector this writes out the actual 8 bytes that are stored in memory for the double.
            // Double precision IEEE-754 floating point number
            // in little endian format

            // http://kirkwylie.blogspot.com/2008/11/ieee-754-floating-point-binary.html

            _bw.Write(d);
        }

        public void Write(Boolean b)
        {
            _bw.Write((byte)(b ? 1 : 0));
            // Same as _bw.Write(bool); but I just want to be explicit so I know what it is really doing.
        }

        public void Write(DateTime dt)
        {
            // The number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001.

            _bw.Write(dt.Ticks); // Int64
        }

        public void Write(TimeSpan ts)
        {
            _bw.Write(ts.Ticks); // Int64
        }

        public void WriteByteArrayWithLength(byte[] data)
        {
            _bw.Write((Int32)data.Length);
            _bw.Write(data);
        }

        public void Write(Image img)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                img.Save(ms, ImageFormat.Png);
                byte[] b = ms.ToArray();
                WriteByteArrayWithLength(b);
            }
        }
        
        public byte[] ToArray()
        {
            _bw.Flush();
            return _ms.ToArray();
        }


        public void Dispose()
        {
            if (_bw != null)
            {
                _bw.Flush();
                _bw.Close();
                _bw = null;
            }

            if (_ms != null)
            {
                _ms.Flush();
                _ms.Close();
                _ms = null;
            }
        }
    }

    public class BinaryStreamReader : IDisposable
    {
        Stream _ms;
        BinaryReader _br; // BinaryReader reads data in little endian format.
        bool _dispose;

        public BinaryStreamReader(byte[] data)
        {
            _ms = new MemoryStream(data);
            _br = new BinaryReader(_ms, Encoding.UTF8);
            _dispose = true;
        }

        public BinaryStreamReader(Stream ms)
        {
            _ms = ms;
            _br = new BinaryReader(_ms, Encoding.UTF8);
            _dispose = false;
        }

        public Rectangle ReadRectangle()
        {
            return new Rectangle(
                (int)_br.ReadInt16(),
                (int)_br.ReadInt16(),
                (int)_br.ReadInt16(),
                (int)_br.ReadInt16());
        }

        public Point ReadPoint()
        {
            return new Point(
                (int)_br.ReadInt16(),
                (int)_br.ReadInt16());
        }

        public Size ReadSize()
        {
            return new Size(
                (int)_br.ReadInt16(),
                (int)_br.ReadInt16());
        }

        public Color ReadColor()
        {
            return Color.FromArgb(
                (int)_br.ReadByte(),
                (int)_br.ReadByte(),
                (int)_br.ReadByte(),
                (int)_br.ReadByte());
        }

        public Font ReadFont()
        {
            return new Font(
                this.ReadString(),
                this.ReadFloat(),
                (FontStyle)_br.ReadByte());
        }

        public string ReadString()
        {
            Int32 length = _br.ReadInt32();

            byte[] utf8 = _br.ReadBytes(length); // no BOM

            return Encoding.UTF8.GetString(utf8, 0, length);
        }

        public byte ReadByte()
        {
            return _br.ReadByte();
        }

        public UInt16 ReadUInt16()
        {
            return _br.ReadUInt16();
        }

        public Int32 ReadInt32()
        {
            return _br.ReadInt32();
        }

        public UInt32 ReadUInt32()
        {
            return _br.ReadUInt32();
        }

        public float ReadFloat()
        {
            // Based on Reflector this writes out the actual 4 bytes that are stored in memory for the float.
            // Single precision IEEE-754 floating point number
            // in little endian format

            // http://kirkwylie.blogspot.com/2008/11/ieee-754-floating-point-binary.html

            return _br.ReadSingle();
        }

        public double ReadDouble()
        {
            // Based on Reflector this writes out the actual 8 bytes that are stored in memory for the double.
            // Double precision IEEE-754 floating point number
            // in little endian format

            // http://kirkwylie.blogspot.com/2008/11/ieee-754-floating-point-binary.html

            return _br.ReadDouble();
        }

        public bool ReadBoolean()
        {
            return (bool)(_br.ReadByte() != 0);
            // Same as _br.ReadBoolean(); but I just want to be explicit so I know what it is really doing.
        }

        public DateTime ReadDateTime()
        {
            // The number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001.

            Int64 ticks = _br.ReadInt64();

            return new DateTime(ticks);
        }

        public TimeSpan ReadTimeSpan()
        {
            Int64 ticks = _br.ReadInt64();

            return new TimeSpan(ticks);
        }

        public byte[] ReadByteArrayWithLength()
        {
            Int32 length = _br.ReadInt32();
            return _br.ReadBytes(length);
        }

        public MemoryStreamImage ReadImage()
        {
            byte[] b = ReadByteArrayWithLength();

            return new MemoryStreamImage(b);
        }

        public void Dispose()
        {
            if (_br != null && _dispose)
            {
                _br.Close(); // WARNING! This just closes the underlying stream. Using Reflector you can see that nothing in the BinaryReader is disposed of.
                _br = null;
            }
        }
    }
}
